#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Protocol Buffers支持模块
处理proto文件的编译和代码生成
"""

import os
import logging
from pathlib import Path
from typing import List, Dict, Optional
from SCons.Script import *

class ProtoType:
    """Proto版本类型"""
    FULL = "full"  # 完整版proto
    LITE = "lite"  # 精简版proto
    MIN = "min"    # C语言版本的proto

class ProtoSupport:
    """Protocol Buffers支持类"""
    
    def __init__(self, env):
        """
        初始化Proto支持
        
        Args:
            env: SCons环境
        """
        self.env = env
        self.protoc_path = self._get_protoc_path()
        self.protoc_c_path = self._get_protoc_c_path()
        self.protoc_lib_path = self._get_protoc_lib_path()
        self.protoc_c_lib_path = self._get_protoc_c_lib_path()
        
    def _get_protoc_path(self) -> str:
        """
        获取protoc编译器路径
        
        Returns:
            protoc编译器路径
        """
        # 尝试从环境变量获取
        protoc_path = os.environ.get('PROTOC_PATH')
        if protoc_path and os.path.isfile(protoc_path):
            return protoc_path
        
        # 尝试从常见路径查找，优先使用系统版本
        common_paths = [
            '/usr/bin/protoc',
            '/usr/local/bin/protoc',
            '/opt/protobuf/bin/protoc'
        ]
        
        for path in common_paths:
            if os.path.isfile(path):
                return path
        
        # 默认路径
        return 'protoc'
    
    def _get_protoc_c_path(self) -> str:
        """
        获取protoc-c编译器路径
        
        Returns:
            protoc-c编译器路径
        """
        # 尝试从环境变量获取（优先级最高，即使文件不存在也使用）
        protoc_c_path = os.environ.get('PROTOC_C_PATH')
        if protoc_c_path:
            return protoc_c_path
        
        # 尝试从常见路径查找，优先使用系统版本
        common_paths = [
            '/usr/bin/protoc-c',
            '/usr/local/bin/protoc-c',
            '/opt/protobuf-c/bin/protoc-c'
        ]
        
        for path in common_paths:
            if os.path.isfile(path):
                return path
        
        # 默认路径
        return 'protoc-c'
    
    def _get_protoc_lib_path(self) -> Optional[str]:
        """
        获取protoc动态库路径
        
        Returns:
            protoc动态库路径，如果未设置则返回None
        """
        return os.environ.get('PROTOC_LIB_PATH')
    
    def _get_protoc_c_lib_path(self) -> Optional[str]:
        """
        获取protoc-c动态库路径
        
        Returns:
            protoc-c动态库路径，如果未设置则返回None
        """
        return os.environ.get('PROTOC_C_LIB_PATH')
    
    def generate_proto_sources(self, proto_files: List[str], output_dir: str, 
                              proto_path: str = None, proto_type: str = 'cpp') -> Dict[str, List[str]]:
        """
        生成proto源代码文件
        
        Args:
            proto_files: proto文件列表
            output_dir: 输出目录
            proto_path: proto搜索路径（已弃用，自动从proto_files提取）
            proto_type: proto类型
            
        Returns:
            生成的源文件和头文件字典
        """
        if not self.validate_proto_files(proto_files):
            return {'sources': [], 'headers': [], 'commands': []}
        
        generated_sources = []
        generated_headers = []
        generated_commands = []
        
        # 自动提取proto文件的根目录作为公共基础路径
        proto_dirs = self.extract_proto_directories(proto_files)
        if proto_dirs:
            # 找到所有proto目录的公共父目录
            common_base = os.path.commonpath(proto_dirs + [os.path.dirname(f) for f in proto_files])
        else:
            # 如果没有目录信息，使用当前工作目录
            common_base = os.getcwd()
        
        for proto_file in proto_files:
            proto_path_abs = os.path.abspath(proto_file)
            # 计算proto文件相对于公共基础路径的相对路径
            try:
                proto_rel_path = os.path.relpath(proto_file, common_base)
                proto_rel_path = os.path.splitext(proto_rel_path)[0]  # 移除.proto扩展名
            except ValueError:
                # 如果无法计算相对路径，使用文件名
                proto_rel_path = os.path.splitext(os.path.basename(proto_file))[0]
            proto_name = os.path.splitext(os.path.basename(proto_file))[0]  # 只用于日志
            
            # 调试信息
            logging.info(f"Processing proto file: {proto_file}")
            logging.info(f"Proto rel path: {proto_rel_path}")
            logging.info(f"Output dir: {output_dir}")
            
            # 根据proto类型生成不同的文件
            if proto_type == 'cpp':
                source_file = os.path.join(output_dir, proto_rel_path + '.pb.cc')
                header_file = os.path.join(output_dir, proto_rel_path + '.pb.h')
            elif proto_type == 'c':
                source_file = os.path.join(output_dir, proto_rel_path + '.pb-c.c')
                header_file = os.path.join(output_dir, proto_rel_path + '.pb-c.h')
            elif proto_type == 'min':  # C语言版本的proto（使用protoc-c）
                source_file = os.path.join(output_dir, proto_rel_path + '.pb-c.c')
                header_file = os.path.join(output_dir, proto_rel_path + '.pb-c.h')
            else:
                raise ValueError(f"Unsupported proto type: {proto_type}. Supported types: cpp, c, min")
                
            # 确保输出文件的目录存在
            os.makedirs(os.path.dirname(source_file), exist_ok=True)
            
            # 构建protoc命令
            if proto_type == 'cpp':
                cmd = self._build_protoc_command(proto_path_abs, output_dir, common_base, proto_type)
            elif proto_type in ['c', 'min']:  # C语言版本都使用protoc-c
                cmd = self._build_protoc_c_command(proto_path_abs, output_dir, common_base)
            
            # 创建SCons构建规则
            proto_target = self.env.Command(
                target=[source_file, header_file],
                source=proto_path_abs,
                action=cmd
            )
            
            generated_sources.append(source_file)
            generated_headers.append(header_file)
            generated_commands.append(proto_target)
            
            logging.info(f"Proto generation rule created: {proto_file} -> {source_file}, {header_file}")
        
        return {
            'sources': generated_sources,
            'headers': generated_headers,
            'commands': generated_commands
        }
    
    def _build_protoc_command(self, proto_file: str, output_dir: str, 
                            proto_path: str = None, proto_type: str = 'cpp') -> str:
        """
        构建protoc命令
        
        Args:
            proto_file: proto文件路径
            output_dir: 输出目录
            proto_path: proto搜索路径
            proto_type: proto类型
            
        Returns:
            protoc命令字符串
        """
        cmd_parts = []
        
        # 自动设置LD_LIBRARY_PATH，包含protobuf和protobuf-c的库路径
        lib_paths = []
        build_project_root = os.environ.get('BUILD_PROJECT_ROOT')
        if build_project_root:
            # 添加protobuf库路径
            protobuf_lib_path = os.path.join(build_project_root, 'tools', 'protobuf', 'lib')
            lib_paths.append(protobuf_lib_path)
            # 添加protobuf-c库路径
            protobuf_c_lib_path = os.path.join(build_project_root, 'tools', 'protobuf-c', 'lib')
            lib_paths.append(protobuf_c_lib_path)
        else:
            # 如果BUILD_PROJECT_ROOT未设置，回退到原有逻辑
            if self.protoc_lib_path:
                lib_paths.append(self.protoc_lib_path)
            if self.protoc_c_lib_path:
                lib_paths.append(self.protoc_c_lib_path)
        
        if lib_paths:
            ld_library_path = ':'.join(lib_paths) + ':$LD_LIBRARY_PATH'
            cmd_parts.append(f"LD_LIBRARY_PATH={ld_library_path}")
        
        cmd_parts.append(self.protoc_path)
        
        # 添加proto搜索路径和确定proto文件参数
        if proto_path:
            cmd_parts.append(f"--proto_path={proto_path}")
            # 当指定了proto_path时，proto文件参数应该是相对于proto_path的路径
            # 计算proto_file相对于proto_path的相对路径
            try:
                proto_file_rel = os.path.relpath(proto_file, proto_path)
                proto_file_arg = proto_file_rel
            except ValueError:
                # 如果无法计算相对路径，使用绝对路径
                proto_file_arg = proto_file
        else:
            # 默认使用proto文件所在目录
            proto_dir = os.path.dirname(proto_file)
            cmd_parts.append(f"--proto_path={proto_dir}")
            proto_file_arg = proto_file
        
        # 添加输出目录
        cmd_parts.append(f"--cpp_out={output_dir}")
        
        # 根据proto类型添加特定选项
        if proto_type == 'lite':
            cmd_parts.append("--cpp_opt=lite")
        
        # 添加proto文件
        cmd_parts.append(proto_file_arg)
        
        return ' '.join(cmd_parts)
    
    def _build_protoc_c_command(self, proto_file: str, output_dir: str, 
                              proto_path: str = None) -> str:
        """
        构建protoc-c命令（用于C语言版本）
        
        Args:
            proto_file: proto文件路径
            output_dir: 输出目录
            proto_path: proto搜索路径
            
        Returns:
            protoc-c命令字符串
        """
        cmd_parts = []
        
        # 自动设置LD_LIBRARY_PATH，包含protobuf和protobuf-c的库路径
        lib_paths = []
        build_project_root = os.environ.get('BUILD_PROJECT_ROOT')
        if build_project_root:
            # 添加protobuf库路径
            protobuf_lib_path = os.path.join(build_project_root, 'tools', 'protobuf', 'lib')
            lib_paths.append(protobuf_lib_path)
            # 添加protobuf-c库路径
            protobuf_c_lib_path = os.path.join(build_project_root, 'tools', 'protobuf-c', 'lib')
            lib_paths.append(protobuf_c_lib_path)
        else:
            # 如果BUILD_PROJECT_ROOT未设置，回退到原有逻辑
            if self.protoc_lib_path:
                lib_paths.append(self.protoc_lib_path)
            if self.protoc_c_lib_path:
                lib_paths.append(self.protoc_c_lib_path)
        
        if lib_paths:
            ld_library_path = ':'.join(lib_paths) + ':$LD_LIBRARY_PATH'
            cmd_parts.append(f"LD_LIBRARY_PATH={ld_library_path}")
        
        # 优先使用PROTOC_C_PATH环境变量指定的protoc-c路径
        cmd_parts.append(self.protoc_c_path)
        
        # 检查protoc-c是否可用
        if not (os.path.isfile(self.protoc_c_path) or self.env.WhereIs(self.protoc_c_path)):
            logging.warning(f"protoc-c not found at {self.protoc_c_path}, please check PROTOC_C_PATH environment variable")
            # 如果protoc-c不可用，抛出错误而不是回退到protoc
            raise FileNotFoundError(f"protoc-c compiler not found at {self.protoc_c_path}. Please install protobuf-c and set PROTOC_C_PATH environment variable.")
            
        # 添加proto搜索路径和确定proto文件参数
        if proto_path:
            cmd_parts.append(f"--proto_path={proto_path}")
            # 当指定了proto_path时，proto文件参数应该是相对于proto_path的路径
            try:
                proto_file_rel = os.path.relpath(proto_file, proto_path)
                proto_file_arg = proto_file_rel
            except ValueError:
                # 如果无法计算相对路径，使用绝对路径
                proto_file_arg = proto_file
        else:
            proto_dir = os.path.dirname(proto_file)
            cmd_parts.append(f"--proto_path={proto_dir}")
            proto_file_arg = proto_file
        
        # 添加输出目录（protoc-c使用--c_out参数）
        cmd_parts.append(f"--c_out={output_dir}")
        
        # 添加proto文件
        cmd_parts.append(proto_file_arg)
        
        return ' '.join(cmd_parts)
    
    def extract_proto_directories(self, proto_files: List[str]) -> List[str]:
        """
        从proto文件列表中提取所有唯一的目录路径
        
        Args:
            proto_files: proto文件列表
            
        Returns:
            唯一目录路径列表
        """
        directories = set()
        for proto_file in proto_files:
            proto_dir = os.path.dirname(proto_file)
            if proto_dir:  # 如果目录不为空
                directories.add(os.path.abspath(proto_dir))
        return list(directories)
    
    def get_proto_include_paths(self, output_dir: str, proto_files: List[str] = None) -> List[str]:
        """
        获取proto生成文件的头文件路径
        
        Args:
            output_dir: proto输出目录
            proto_files: proto文件列表（可选，用于提取额外的包含路径）
            
        Returns:
            头文件路径列表
        """
        include_paths = [output_dir]
        
        # 如果提供了proto_files，添加从proto文件中提取的目录到输出目录的映射
        if proto_files:
            proto_dirs = self.extract_proto_directories(proto_files)
            if proto_dirs:
                # 找到所有proto目录的公共父目录
                try:
                    common_base = os.path.commonpath(proto_dirs + [os.path.dirname(f) for f in proto_files])
                    for proto_dir in proto_dirs:
                        # 计算proto目录相对于公共基础路径的相对路径
                        relative_dir = os.path.relpath(proto_dir, common_base)
                        if relative_dir and relative_dir != '.':
                            # 将相对路径映射到输出目录
                            mapped_dir = os.path.join(output_dir, relative_dir)
                            if mapped_dir not in include_paths:
                                include_paths.append(mapped_dir)
                except ValueError:
                    # 如果无法计算公共路径，直接使用输出目录
                    pass
        
        return include_paths
    
    def validate_proto_files(self, proto_files: List[str]) -> bool:
        """
        验证proto文件是否存在且有效
        
        Args:
            proto_files: proto文件列表
            
        Returns:
            是否所有文件都有效
        """
        for proto_file in proto_files:
            if not os.path.exists(proto_file):
                logging.error(f"Proto file not found: {proto_file}")
                return False
            
            if not proto_file.endswith('.proto'):
                logging.error(f"Invalid proto file extension: {proto_file}")
                return False
        
        return True

def generate(env):
    """
    生成proto支持工具
    
    Args:
        env: SCons环境
    """
    proto_support = ProtoSupport(env)
    env['PROTO_SUPPORT'] = proto_support
    
    logging.info("Proto support tool loaded")

def exists(env):
    """
    检查工具是否存在
    
    Args:
        env: SCons环境
        
    Returns:
        True
    """
    return True